package proyectoX.src.controles;

import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.HashMap;
import java.util.Random;
import java.util.Vector;

import proyectoX.src.aplicacion.Juego;
import proyectoX.src.entidades.Enemiga;
import proyectoX.src.entidades.Principal;
import proyectoX.src.municiones.MunicionEspejo;
import proyectoX.src.municiones.MunicionX;
import proyectoX.src.powerups.Power_up;
import proyectoX.src.TDAPair.Pair; 

/**
 * Clase central del juego donde se procesan las colisiones, desplazamientos, creaciones de enemigos y el input por teclado.
 *
 */
public class ControlOmega extends control implements KeyListener {

	protected Vector<MunicionX> municionesAInsertar;
	protected Vector<MunicionX> municionesActuales;
	protected Vector<MunicionX> municionesAEliminar;

	protected Vector<Power_up> powerupsAInsertar;
	protected Vector<Power_up> powerupsActuales;
	protected Vector<Power_up> powerupsAEliminar;

	protected Vector<Enemiga> enemigasAInsertar;
	protected Vector<Enemiga> enemigasActuales;
	protected Vector<Enemiga> enemigasAEliminar;
	protected static final int  MAX_NAVESENPANTALLA = 5;
	protected int contNaves;

	//cada código de teclado está asociado a una acción (string) y a un índice del arreglo (integer)
	protected HashMap<Integer, Pair<String, Integer>> mapeoTeclado;  
	protected Vector<Integer> teclasAInsertar;
	protected Pair<String ,Boolean> [] teclasPresionadas;  //flags si las teclas están presionadas o no
	protected Vector<Integer> teclasAEliminar;
	protected Vector<Integer> teclasActuales; //teclas a procesar
	protected boolean agregandoTeclas;
	protected boolean eliminandoTeclas;
	protected boolean limpiarTeclas;
	protected boolean enPausa; 

	protected Principal nave;


	protected boolean limpiarPantalla;
	protected boolean eliminarTodasEnemigas;

	protected Random generador;

	/**
	 * Crea al objeto e inicializa a todos los atributos.
	 */
	public ControlOmega(Juego j) {
		this.juego = j;
		nave = juego.getNivelActual().obtenerNavePrincipal();
		contNaves=0;

		municionesAInsertar = new Vector<MunicionX>();
		municionesActuales = new Vector<MunicionX>();
		municionesAEliminar = new Vector<MunicionX>();

		powerupsAInsertar = new Vector<Power_up>();
		powerupsActuales = new Vector<Power_up>();
		powerupsAEliminar = new Vector<Power_up>();

		enemigasAInsertar = new Vector<Enemiga>();
		enemigasActuales = new Vector<Enemiga>();
		enemigasAEliminar = new Vector<Enemiga>();

		generador = new Random();

		limpiarPantalla = false;
		eliminarTodasEnemigas = false;

		//para procesar las teclas
		enPausa = false;
		agregandoTeclas = false;
		eliminandoTeclas = false;
		limpiarTeclas = false;
		mapeoTeclado = new HashMap<Integer, Pair<String,Integer>>();
		inicializarMapeoTeclado();
		teclasAInsertar = new Vector<Integer>();
		teclasAEliminar = new Vector<Integer>();

	}

	/**
	 * Ciclo central del juego. 
	 * Primero se agregan todos los objetos a las colecciones correspondientes.
	 * Luego se procesan las teclas, luego la lógica del nivel actual, las naves enemigas, las municiones
	 * y por último los powerups. 
	 * Por último se eliminan los objetos de las colecciones como sea requerido.
	 */
	public void run() {
		try {

			while(juego.obtenerJuegoCorriendo()) {

				if (limpiarTeclas) {
					limpiarLasTeclas();
					limpiarTeclas = false;
				}


				agregandoTeclas = true;		//flag para evitar modificación concurrente
				agregarTeclasAgregables();
				agregandoTeclas = false;
				agregarEnemigasAgregables();
				agregarMunicionesAgregables();
				agregarPowerupsAgregables();

				//primero procesar las teclas
				procesarTeclas();

				//LÓGICA DEL NIVEL ACTUAL
				juego.getNivelActual().getFabricaNivel().crearEnemigos();

				
				//BLOQUE ENEMIGAS
				for(Enemiga e: enemigasActuales){	
					int moneda= generador.nextInt(15);
					if(moneda==0){
						e.disparar(e.getLocation());
					}
					//reviso si esta congelado
					if(!juego.getNivelActual().NivelCongelado()){
						e.mover();	}			



					if(juego.estaFueraDePantalla(e)){
						eliminarEnemiga(e);	}
					else{
						Rectangle areaColisionPrincipal = nave.getBounds();	
						if (e.getBounds().intersects(areaColisionPrincipal)) {
							e.chocar();								
						}
					}				

					if (limpiarPantalla || eliminarTodasEnemigas) {
						eliminarEnemiga(e);
					}
				}


				//EMPIEZA BLOQUE MUNICIONES
				for (MunicionX m : municionesActuales) {
					//reviso si esta congelado

					m.actualizarPosicion();	


					boolean colisiono=m.calcularColisiones();					
					if ((colisiono)||(juego.estaFueraDePantalla(m))) {
						this.eliminarMunicion(m);						
						juego.eliminarGraficamenteDelNivel(m);
					}
					if (limpiarPantalla) {
						eliminarMunicion(m);
					}
				}		

				//BLOQUE POWERUPS
				for (Power_up p : powerupsActuales) {

					//reviso si esta congelado
					if(!juego.getNivelActual().NivelCongelado()){
						p.actualizarPosicion();}

					boolean colisiono = p.calcularColisiones();					

					if ((colisiono)||(juego.estaFueraDePantalla(p))) {
						this.eliminarPowerup(p);						
						juego.eliminarGraficamenteDelNivel(p.getIcono());
					}
				}

				eliminandoTeclas = true;
				eliminarTeclasEliminables();
				eliminandoTeclas = false;
				eliminarEnemigasEliminables();
				eliminarMunicionesEliminables();
				eliminarPowerupsEliminables();
				limpiarPantalla = false;
				eliminarTodasEnemigas = false;

				this.sleep(30);
			}

			eliminarTodoGraficamente();
		}
		catch (InterruptedException ex) {
		}
	}

	/**
	 * Setea el atributo para limpiar la pantalla en verdadero.
	 */
	public void limpiarPantalla(){
		limpiarPantalla = true;		
	}

	/**
	 * Setea el atributo para eliminar todos los enemigos en verdadero.
	 */
	public void eliminarTodasLasEnemigas() {
		eliminarTodasEnemigas = true;
	}

	/**
	 * Agrega una nave enemiga a la colección para ser insertadas.
	 * @param e Nave enemiga a insertar.
	 */
	public void agregarEnemiga(Enemiga e) {
		enemigasAInsertar.add(e);
		contNaves++;
	}

	/**
	 * Agrega una nave enemiga a la colección para ser eliminadas y decrementa la cantidad de naves en pantalla.
	 * @param e Nave enemiga a eliminar.
	 */
	public void eliminarEnemiga(Enemiga e) {
		enemigasAEliminar.add(e);
		contNaves--;
	}

	/**
	 * Agrega una munición a la colección para ser insertadas.
	 * @param m Munición a insertar.
	 */
	public void agregarMunicion(MunicionX m) {
		municionesAInsertar.add(m);
	}

	/**
	 * Agrega una munición a la colección para ser eliminadas.
	 * @param m Munición a eliminar.
	 */
	public void eliminarMunicion(MunicionX m) {
		municionesAEliminar.add(m);
	}

	/**
	 * Agrega un powerup a la colección para ser insertadas.
	 * @param p Powerup a insertar.
	 */
	public void agregarPowerup(Power_up p) {
		powerupsAInsertar.add(p);
	}

	/**
	 * Agrega un powerup a la colección para ser eliminadas.
	 * @param p Powerup a eliminar.
	 */
	public void eliminarPowerup(Power_up p) {
		powerupsAEliminar.add(p);
	}

	/**
	 * Agrega todas las enemigas a agregar a las enemigas actuales.
	 */
	private void agregarEnemigasAgregables() {
		for (Enemiga e : enemigasAInsertar) {
			enemigasActuales.add(e);
		}

		enemigasAInsertar.clear();
	}

	/**
	 * Agrega todas las municiones a agregar a las municiones actuales.
	 */
	private void agregarMunicionesAgregables() {
		for (MunicionX m : municionesAInsertar) {
			municionesActuales.add(m);
		}

		municionesAInsertar.clear();
	}

	/**
	 * Elimina todas las enemigas a eliminar de las municiones actuales.
	 */
	private void eliminarEnemigasEliminables() {
		for (Enemiga e : enemigasAEliminar) {
			enemigasActuales.remove(e);
			juego.eliminarGraficamenteDelNivel(e);
		}

		enemigasAEliminar.clear();
	}

	/**
	 * Elimina todas las municiones a eliminar de las municiones actuales.
	 */
	private void eliminarMunicionesEliminables() {
		for (MunicionX m : municionesAEliminar) {
			municionesActuales.remove(m);
			juego.eliminarGraficamenteDelNivel(m);
		}

		municionesAEliminar.clear();
	}

	/**
	 * Agrega todos las powerups a agregar a las municiones actuales.
	 */
	private void agregarPowerupsAgregables() {
		for (Power_up p : powerupsAInsertar) {
			powerupsActuales.add(p);
		}

		powerupsAInsertar.clear();
	}

	/**
	 * Elimina todos los powerups a eliminar de los powerups actuales.
	 */
	private void eliminarPowerupsEliminables() {
		for (Power_up p : powerupsAEliminar) {
			powerupsActuales.remove(p);
			juego.eliminarGraficamenteDelNivel(p.getIcono());
		}

		municionesAEliminar.clear();
	}

	/**
	 * Controla colisiones entre la munición pasada por parámetro y las naves enemigas.
	 * @param m Munición a colisionar.
	 * @return Verdadero si colisiona, falso en caso contrario.
	 */
	public boolean controlarColicionNavesEnemigas( MunicionX m){
		Rectangle areaColision = m.getBounds();
		boolean colisiono=false;
		for (Enemiga e :  enemigasActuales) {
			if (e.getBounds().intersects(areaColision)) {
				juego.getNivelActual().sumarPuntaje(e.getPuntaje());
				e.recibirDisparo(m.getDanio());							
				colisiono = true;
			}
		}
		return colisiono;
	}

	/**
	 * Controla colisiones entre la munición pasada por parámetro y la nave principal.
	 * @param m Munición a colisionar.
	 * @return Verdadero si colisiona, falso en caso contrario.
	 */
	public boolean controlarColicionNavePrincipal( MunicionX m){
		Rectangle areaColision = m.getBounds();
		boolean colisiono=false;		

		if (nave.getBounds().intersects(areaColision)) {				
			nave.recibirDisparo(m.getDanio());						
			colisiono = true;
		}
		return colisiono;
	}
	
	/**
	 * Controla colisiones entre el powerup pasado por parámetro y la nave jugadora.
	 * @param p Powerup a colisionar.
	 * @return Verdadero si colisiona, falso en caso contrario.
	 */
	public boolean controlarColisionPowerup(Power_up p) {
		Rectangle areaColision = p.getBounds();
		boolean colisiono = false;		

		if (nave.getBounds().intersects(areaColision)) {
			p.activarEfecto();
			colisiono = true;
		}

		return colisiono;
	}
	
	/**
	 * Controla colisiones entre la MunicionEspejo pasada por parámetro y las otras municiones.
	 * @param m MunicionEspejo a colisionar.
	 * @return Verdadero si colisiona, falso en caso contrario.
	 */
	public boolean controlarColisionMunicion(MunicionEspejo m) {
		Rectangle areaColision = m.getBounds();
		boolean colisiono=false;
		for (MunicionX e :  municionesActuales) {
			if (e.getBounds().intersects(areaColision)&&!(e==m)) {			 						
				e.setVelocidad(-1* e.getVelocidad());
				colisiono = true;
			}
		}
		return colisiono;
	}


	
	/**
	 * Método invocado únicamente al caer en Game Over. Eliina todo gráficamente. 
	 */
	public void eliminarTodoGraficamente() {
		for (Enemiga e : enemigasActuales) {
			juego.eliminarGraficamenteDelNivel(e);
		}
		for (MunicionX m : municionesActuales) {
			juego.eliminarGraficamenteDelNivel(m);
		}
		juego.eliminarGraficamenteDelNivel(juego.getNivelActual().obtenerNavePrincipal());
	}


	//mapeo pensado en agregar la posibildad de cambiar los controles a conveniencia del usuario
	/**
	 * Inicializa el mapeo de teclas. El HashMap es para asociar un valor apretado a un
	 * string y un índice del arreglo de booleanos.
	 * 
	 * El arreglo de booleanos mantiene valores verdaderos en los índices donde la tecla
	 * está presionada y falso en caso contrario. 
	 * 
	 * Procesando este arreglo logramos un movimiento fluído.
	 */
	private void inicializarMapeoTeclado() {
		mapeoTeclado.put(KeyEvent.VK_UP, new Pair<String, Integer>("avanzar", 0));
		mapeoTeclado.put(KeyEvent.VK_DOWN, new Pair<String, Integer>("retroceder", 1));
		mapeoTeclado.put(KeyEvent.VK_LEFT, new Pair<String, Integer>("izquierda", 2));
		mapeoTeclado.put(KeyEvent.VK_RIGHT, new Pair<String, Integer>("derecha", 3));
		mapeoTeclado.put(KeyEvent.VK_Z, new Pair<String, Integer>("principal",4));
		mapeoTeclado.put(KeyEvent.VK_X, new Pair<String, Integer>("secundario", 5));
		mapeoTeclado.put(KeyEvent.VK_C, new Pair<String, Integer>("habilidad", 6));
		mapeoTeclado.put(KeyEvent.VK_V, new Pair<String, Integer>("devastadora", 7));
		mapeoTeclado.put(KeyEvent.VK_R, new Pair<String, Integer>("reiniciar", 8));
		mapeoTeclado.put(KeyEvent.VK_P, new Pair<String, Integer>("pausa", 9));	//todavía debo implementar esto bien
		mapeoTeclado.put(KeyEvent.VK_ESCAPE, new Pair<String, Integer>("salir", 10));	//todavía debo implementar esto bien


		teclasPresionadas = (Pair<String ,Boolean>[]) new Pair[mapeoTeclado.size()];

		teclasPresionadas[0] = new Pair<String, Boolean>("avanzar", false);
		teclasPresionadas[1] = new Pair<String, Boolean>("retroceder", false);
		teclasPresionadas[2] = new Pair<String, Boolean>("izquierda", false);
		teclasPresionadas[3] = new Pair<String, Boolean>("derecha", false);
		teclasPresionadas[4] = new Pair<String, Boolean>("principal", false);
		teclasPresionadas[5] = new Pair<String, Boolean>("secundario", false);
		teclasPresionadas[6] = new Pair<String, Boolean>("habilidad", false);
		teclasPresionadas[7] = new Pair<String, Boolean>("devastadora", false);
		teclasPresionadas[8] = new Pair<String, Boolean>("reiniciar", false);
		teclasPresionadas[9] = new Pair<String, Boolean>("pausa", false);
		teclasPresionadas[10] = new Pair<String, Boolean>("salir", false);
	}

	
	/**
	 * Procesa la tecla individual pasada por parámetro. Si el juego está en pausa,
	 * sólo se procesa "pasua" y "salir".
	 * @param str La tecla a procesar.
	 */
	public void procesarLaTecla(String str) {
		int factor = nave.getVelocidad();
		int posX = nave.getX();
		int posY = nave.getY();
		int dX = posX;
		int dY = posY;

		if (str != null) {

			if (!enPausa) {
				switch(str) {
				case "avanzar": {
					if (posY >= 5) {
						dY -= factor;
					}
					break;
				}
				case "retroceder": {
					if (posY <= juego.getHeight()-nave.getHeight()*2-5) {  //extrañamente parece que la altura de la ventana no es 500
						dY += factor;
					}
					break;
				}
				case "izquierda": {
					if (posX >= 5) {
						dX -= factor;
					}
					break;
				}
				case "derecha": {
					if (posX <= juego.getWidth()-juego.getWidthInterfazIconos()-nave.getWidth()-5) {
						dX += factor;
					}
					break;
				}	
				case "habilidad": {
					nave.disparar(3);
					break;
				}
				case "principal":{	
					nave.disparar(1);
					break;
				}
				case "devastadora":{
					nave.disparar(4);
					break;
				}
				case "secundario":{
					nave.disparar(2);
					break;
				}
				case "reiniciar":{
					juego.reinicio();
					break;
				}
				case "pausa": {
					enPausa = true;
					try {
						synchronized(this) {
							this.wait();
						}
					} 
					catch (InterruptedException e) {

					}
					break;
				}
				case "salir": {
					System.exit(0);	//cerrar el juego
					break;
				}
				}
			}
			else {
				switch(str) {
				case "pausa": {
					enPausa = false;
					synchronized(this) {
						this.notify();
					}
					break;
				}
				case "salir": {
					System.exit(0);	//cerrar el juego
					break;
				}
				}
			}
		}

		nave.setLocation(dX, dY);
	}

	/**
	 * Manda a procesar todas las teclas presionadas.
	 */
	public void procesarTeclas() {
		for (Pair<String, Boolean> p : teclasPresionadas) {
			if (p.getSecond()) {
				procesarLaTecla(p.getFirst());
			}
		}
	}

	@Override
	/**
	 * Se pasa a eliminar la tecla pasada por parámetro. Se inserta en la colección de
	 * eliminaciones.  
	 * 
	 * @param e Tecla liberada.
	 */
	public void keyReleased(KeyEvent e) {
		if (!eliminandoTeclas) {
			teclasAEliminar.add((Integer)e.getKeyCode());
		}
	}

	@Override
	public void keyTyped(KeyEvent e) {
		//No es utilizado
	}

	@Override
	/**
	 * Inserta la tecla pasada por parámetro a la colección de inserción si el juego está en curso
	 * y en caso contrario la manda a procesar individualmente.
	 */
	public void keyPressed(KeyEvent e) {
		if ((!enPausa)&&(juego.obtenerJuegoCorriendo())) {
			if (!agregandoTeclas) {
				teclasAInsertar.add((Integer)e.getKeyCode());
			}
		}
		else {
			//Este else permite que el teclado responda después de que se perdió
			Integer i = e.getKeyCode();
			String str = mapeoTeclado.get(i).getFirst();
			procesarLaTecla(str);
		}
	}

	/**
	 * Procede a agregar a las teclas presionadas a todas las teclas de la colección 
	 * teclasAInsertar.
	 * Uso de synchronized para evitar un pequeño error de concurrencia que ocurre
	 * muy infrecuentemente y que no fue posible detectar a tiempo su causa. 
	 */
	private synchronized void agregarTeclasAgregables() {
		for (Integer i : teclasAInsertar) {
			Pair<String, Integer> p = mapeoTeclado.get(i);
			if (p != null) {
				int pos = p.getSecond();
				teclasPresionadas[pos].setSecond(true);
			}
		}

		teclasAInsertar.clear();
	}


	/**
	 * Setea todas las teclas de la colección teclasAEliminar en falso.
	 * Uso de synchronized para evitar un pequeño error de concurrencia que ocurre
	 * muy infrecuentemente y que no fue posible detectar a tiempo su causa. 
	 */
	private synchronized void eliminarTeclasEliminables() {
		for (Integer i : teclasAEliminar) {
			Pair<String, Integer> p = mapeoTeclado.get(i);
			if (p != null) {
				int pos = p.getSecond();
				teclasPresionadas[pos].setSecond(false);
			}
		}

		teclasAEliminar.clear();
	}
	
	/**
	 * Setea en true el atributo para setear todas las teclas presionadas en false.
	 */
	public void limpiarTeclas() {
		limpiarTeclas = true;
	}
	
	/**
	 * Setea todas las teclas presionables en false.
	 */
	private void limpiarLasTeclas() {
		for (Pair<String, Boolean> p : teclasPresionadas) {
			p.setSecond(false);
		}
	}
    
	public void aumentarContNaves() {
		contNaves++;
	}

	public Enemiga obtenerEnemiga(Point point) {//compara mas cercana con repecto a eje Y
	       Enemiga masCerca=null;	      
		for (Enemiga e :  enemigasActuales) {
			if (masCerca==null) {
				masCerca=e;
			}
			else{
				double difX=Math.abs(point.getX()-e.getX());
				double difX2=Math.abs(point.getX()-masCerca.getX());
				if(difX<=difX2){
					masCerca=e;
					}
				}
			}
		return masCerca;
	}

}
